/*
    Meter JMX Utility Library
    Copyright (C) 2005  Noah Campbell

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package info.noahcampbell.meter;



import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math.stat.descriptive.StatisticalSummary;
import org.apache.commons.math.stat.descriptive.SummaryStatistics;


/**
 * @author Noah Campbell
 * @version 1.0
 */
public class MeterObserver extends MetricObserver<Meter, MeterObserver.Stats> {
    
    /** The stats. */
    private Set<Stats> stats;
    
    /**
     * @see MetricObserver#newInstance()
     */
    @Override
    protected synchronized Stats newInstance() {
        if(stats == null) {
            stats = new HashSet<Stats>();
        }
        Stats s = new Stats();
        stats.add(s);
        return s;
    }

    /**
     * @param number 
     * @param obj 
     * @see MetricObserver#process(T, V)
     */
    @Override
    protected void process(Number number, Stats obj) {
        obj.ds.addValue(number.doubleValue());
        obj.ss.addValue(number.doubleValue());
    }

    /**
     * Construct a new <code>MeterObserver</code>.
     *
     * @param meters
     * @throws IllegalArgumentException If less than 1 <code>Meter</code> is provided to observe.
     */
    public MeterObserver(Collection<Meter> meters) throws IllegalArgumentException {
        super(meters);
    }
    
    /**
     * Construct a new <code>MeterObserver</code>.
     *
     * @param meters
     * @throws IllegalArgumentException
     */
    public MeterObserver(Meter...meters) throws IllegalArgumentException {
        this(Arrays.asList(meters));
    }
    
    /**
     * Construct a new <code>MeterObserver</code>.
     *
     */
    public MeterObserver() {
    }
    
    
    /**
     * @param m
     */
    protected void addMeter(Meter m) {
        this.addMetric(m);
    }
    
    
    /** The statisticalSummaryType. */
    private static CompositeType statisticalSummaryType;
    
    /**
     * @return sample
     * @see XMPPParserMBean#getSampleWindow()
     */
    public int getSampleWindow() {
        
        Iterator<Stats> iterator = this.stats.iterator();
        if(iterator.hasNext())
            return iterator.next().ds.getWindowSize();
        else
            return -1;
    }

    /**
     * @param sample 
     * @see XMPPParserMBean#setSampleWindow(int)
     */
    public void setSampleWindow(int sample) {
        for(Stats s : this.stats) {
            s.ds.setWindowSize(sample);
        }
    }
    
    
    /**
     * @param m
     * @return compositeData
     * @throws OpenDataException
     */
    protected CompositeData getSummaryData(Meter m) throws OpenDataException {
        CompositeDataSupport cds = new CompositeDataSupport(statisticalSummaryType,
                getValueMap(getStats(m).ss.getSummary()));
        
        return cds;
    }

    
    /**
     * @param m
     * @return compositeData
     * @throws OpenDataException
     */
    protected CompositeData getDescriptiveData(Meter m) throws OpenDataException {
        CompositeDataSupport cds = new CompositeDataSupport(statisticalSummaryType,
                getValueMap(getStats(m).ds));
        
        return cds;
    }
    
    /**
     * @param ss
     * @return map 
     */
    @SuppressWarnings("unchecked")
    private Map getValueMap(StatisticalSummary ss) {
        
        Map map = new HashMap(7, 1.0f);
        map.put("max", ss.getMax());
        map.put("mean", ss.getMean());
        map.put("min", ss.getMin());
        map.put("n", ss.getN());
        map.put("stddiv", ss.getStandardDeviation());
        map.put("sum", ss.getSum());
        map.put("var", ss.getVariance());
        return map;
    }
    
    

    static {
        String[] names = {"max", "mean", "min", "n", "stddiv", "sum", "var"};
        String[] description = {"max", "mean", "min", "n", "stddiv", "sum", "var"};
        OpenType[] types = {SimpleType.DOUBLE,
                SimpleType.DOUBLE,
                SimpleType.DOUBLE,
                SimpleType.LONG,
                SimpleType.DOUBLE,
                SimpleType.DOUBLE,
                SimpleType.DOUBLE};
        try {
            statisticalSummaryType = new CompositeType(StatisticalSummary.class.getName(),
                    "statistical summary", names, description, types );
        } catch (OpenDataException e) {
            
        }
    }
    
    /**
     * @author Noah Campbell
     * @version 1.0
     */
    static class Stats {
        
        /** The ds. */
        public final DescriptiveStatistics ds;
        
        /** The ss. */
        public final SummaryStatistics ss;
        
        /**
         * Construct a new <code>Stats</code>.
         *
         * @param ds
         * @param ss
         */
        public Stats(DescriptiveStatistics ds, SummaryStatistics ss) {
            this.ds = ds;
            // limit the size of the window so we don't
            // eat up all the memory over time
            ds.setWindowSize(1000); 
            this.ss = ss;
        }
        
        /**
         * Construct a new <code>Stats</code>.
         *
         */
        public Stats() {
            this(DescriptiveStatistics.newInstance(), SummaryStatistics.newInstance());
        }
    }

}
